/**
 * elancom Corporation copy right 2012 - 2015  
 *   
 * $Header: https://192.168.1.224/svn/elancom/product/elancom-platform/trunk/src/main/java/com/elancom/framework/commons/json/parser/deserializer/MapDeserializer.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Id: MapDeserializer.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Author: lanzhouxue $
 * $Date: 2012-07-02 11:09:12 +0800 (Mon, 02 Jul 2012) $
 * $Revision: 1034 $
 */
package com.elancom.framework.commons.json.parser.deserializer;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.elancom.framework.commons.json.JSONException;
import com.elancom.framework.commons.json.parser.DefaultJSONParser;
import com.elancom.framework.commons.json.parser.JSONLexer;
import com.elancom.framework.commons.json.parser.JSONToken;
import com.elancom.framework.commons.json.parser.ParseContext;
import com.elancom.framework.commons.json.util.AnticollisionHashMap;

public class MapDeserializer implements ObjectDeserializer {

	public final static MapDeserializer instance = new MapDeserializer();

	@Override
	@SuppressWarnings("unchecked")
	public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
		final JSONLexer lexer = parser.getLexer();
		if (lexer.token() == JSONToken.NULL) {
			lexer.nextToken(JSONToken.COMMA);
			return null;
		}

		final Map<Object, Object> map = createMap(type);

		final ParseContext context = parser.getContext();

		try {
			parser.setContext(context, map, fieldName);

			if (lexer.token() == JSONToken.RBRACE) {
				lexer.nextToken(JSONToken.COMMA);
				return (T) map;
			}

			return (T) deserialze(parser, type, fieldName, map);
		} finally {
			parser.setContext(context);
		}
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected Object deserialze(DefaultJSONParser parser, Type type, Object fieldName, Map map) {
		if (type instanceof ParameterizedType) {
			final ParameterizedType parameterizedType = (ParameterizedType) type;
			final Type keyType = parameterizedType.getActualTypeArguments()[0];
			final Type valueType = parameterizedType.getActualTypeArguments()[1];

			if (String.class == keyType) {
				return DefaultObjectDeserializer.instance.parseMap(parser, map, valueType, fieldName);
			} else {
				return DefaultObjectDeserializer.instance.parseMap(parser, map, keyType, valueType, fieldName);
			}
		} else {
			return parser.parseObject(map, fieldName);
		}
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected Map<Object, Object> createMap(Type type) {
		if (type == Properties.class) {
			return new Properties();
		}

		if (type == Hashtable.class) {
			return new Hashtable();
		}

		if (type == IdentityHashMap.class) {
			return new IdentityHashMap();
		}

		if (type == SortedMap.class || type == TreeMap.class) {
			return new TreeMap();
		}

		if (type == ConcurrentMap.class || type == ConcurrentHashMap.class) {
			return new ConcurrentHashMap();
		}

		if (type == Map.class) {
			return new AnticollisionHashMap();
		}

		if (type == HashMap.class) { //
			return new HashMap();
		}

		if (type == LinkedHashMap.class) {
			return new LinkedHashMap();
		}

		if (type instanceof ParameterizedType) {
			final ParameterizedType parameterizedType = (ParameterizedType) type;

			return createMap(parameterizedType.getRawType());
		}

		if (type instanceof Class<?>) {
			final Class<?> clazz = (Class<?>) type;
			if (clazz.isInterface()) {
				throw new JSONException("unsupport type " + type);
			}

			try {
				return (Map<Object, Object>) clazz.newInstance();
			} catch (final Exception e) {
				throw new JSONException("unsupport type " + type, e);
			}
		}

		throw new JSONException("unsupport type " + type);
	}

	@Override
	public int getFastMatchToken() {
		return JSONToken.LBRACE;
	}
}
